/*
 * Copyright (C) 2009 Dimitrios C. Michalakos
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.cs.util.db;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * GenericDatabaseMetaInfo can be extended by another class to get information about the types and properties of the tables in a database.
 * @author dimitris@jmike.gr
 */
public class GenericDatabaseMetaInfo implements DatabaseMetaInfo {

    protected Map<String, Integer> index = new HashMap<String, Integer>();//<table name, internal id>
    /* @param relations can answer these questions:
    - is table A related to table B?
    - which foreign key is used to connect table A to table B?
    Please note that relations can contain only one foreign key.
    In the rare case of multiple foreign keys between table A and table B, only one foreign key is returned.
    Here is an example of multiple foreign keys between table A and table B...
    - Consider table person with one column: id.
    - Consider table relative with two columns: person1 and person2.
    - Column person1 is a foreign key to table person.
    - Column person2 is a foreign key to table person. */
    protected Map<String, String> relations = new HashMap<String, String>();//<table1-table2, foreign key name>
    protected Map<String, List<Integer>> foreignKeys = new HashMap<String, List<Integer>>();//<foreign key name, list of internal ids>
    protected String[] fkColumn1;
    protected String[] fkColumn2;

    /**
     * Indicates whether the specified table exists in the database.
     * @param table The name of the table.
     * @return true if table exists in database, false if not.
     */
    public boolean containsTable(String table) {
        return index.containsKey(table);
    }

    /**
     * Returns an array containing all the tables of the database.
     * @return A new String array.
     */
    public String[] getTableArray() {
        return index.keySet().toArray(new String[0]);
    }

    /**
     * Indicates whether the specified tables are related.
     * @param t1 The name of the first table.
     * @param t2 The name of the second table.
     * @return true if tables are related, false if not.
     */
    public boolean areTablesRelated(String t1, String t2) {
        return relations.containsKey(t1 + "-" + t2) || relations.containsKey(t2 + "-" + t1);
    }

    /**
     * Returns the foreign key that describes the relation between the designated tables.
     * Plz note that tables must be in the correct order.
     * A foreign key may exist for table1 - table2 relation, but this method will return null if tables are provided in this order: table2 - table1.
     * @param t1 The name of the first table.
     * @param t2 The name of the second table.
     * @return
     */
    public String getForeignKey(String t1, String t2) {
        return relations.get(t1 + "-" + t2);
    }

    /**
     * Returns a two (2) dimensional array containing the columns of the specified foreign key.
     * @param foreignKey The name of the foreign key.
     * @return A two (2) dimensional String array.
     * @throws SQLException
     */
    public String[][] getForeignKeyColumns(String foreignKey) throws SQLException {
        if (foreignKeys.containsKey(foreignKey)) {
            List<Integer> ids = foreignKeys.get(foreignKey);
            String[][] a = new String[ids.size()][2];
            for (int i = 0; i < ids.size(); i++) {//iterate ids
                final int pos = ids.get(i).intValue();
                a[i][0] = fkColumn1[pos];
                a[i][1] = fkColumn2[pos];
            }
            return a;
        } else {
            SqlExceptionState state = SqlExceptionState.FOREIGN_KEY_NOT_FOUND;
            throw new SQLException("Foreign key " + foreignKey + " cannot be found in database.", state.name(), state.code());
        }
    }

    /**
     * Returns a two (2) dimensional array containing the foreign key columns of the related tables.
     * @param t1 The name of the first table.
     * @param t2 The name of the second table.
     * @return A two (2) dimensional String array.
     * @throws SQLException
     */
    public String[][] getForeignKeyColumns(String t1, String t2) throws SQLException {
        String foreignKey = relations.get(t1 + "-" + t2);//try to find relation t1-t2
        if (foreignKey != null) {//relation t1-t2 exists
            List<Integer> ids = foreignKeys.get(foreignKey);
            String[][] a = new String[ids.size()][2];
            for (int i = 0; i < ids.size(); i++) {//iterate ids
                final int pos = ids.get(i).intValue();
                a[i][0] = fkColumn1[pos];
                a[i][1] = fkColumn2[pos];
            }
            return a;
        } else {//relation t1-t2 does not exist
            foreignKey = relations.get(t2 + "-" + t1);//try to find relation t2-t1
            if (foreignKey != null) {//relation t2-t1 exists
                List<Integer> ids = foreignKeys.get(foreignKey);
                String[][] a = new String[ids.size()][2];
                for (int i = 0; i < ids.size(); i++) {//iterate idse
                    final int pos = ids.get(i).intValue();
                    a[i][1] = fkColumn1[pos];
                    a[i][0] = fkColumn2[pos];
                }
                return a;
            } else {//relation between the designates tables does not exist
                SqlExceptionState state = SqlExceptionState.TABLES_NOT_RELATED;
                throw new SQLException("Table " + t1 + " is not related to table " + t2 + ".", state.name(), state.code());
            }
        }
    }
}
